package service.ca;
import iface.CAServiceIface;


import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import common.AnonMath;
import common.Settings;
import common.SignatureCreator;
import common.SignedData;

public class CA implements CAServiceIface {
	// signatures
	PublicKey pubKey;
	PrivateKey privKey;
	SignatureCreator sc;
	
	//Clients
	//private int nextClientId;
	private Set<Integer> clientIDS;
	private Random rand;
	private final Integer MINID = 10000;
	private final Integer MAXID = 99999;

	public CA(){
		//initKeysFromFiles();
		initKeys();
		sc = new SignatureCreator(this.privKey.getEncoded());
		this.clientIDS = new HashSet<Integer>();
		this.rand = new Random();
		//this.nextClientId = 0;
	}

	/**
	 * Initializing private and public keys 
	 */
	private void initKeys() {
		try{
			/* Generate a key pair */
			KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");

			keyGen.initialize(1024, random);

			KeyPair pair = keyGen.generateKeyPair();
			this.privKey = pair.getPrivate();
			this.pubKey = pair.getPublic();
		} catch (Exception e) {
			System.err.println("Caught exception " + e.toString());
			e.printStackTrace();
		}
	}

	/**
	 * Retrieving public and private keys from files 
	 */
	private void initKeysFromFiles() {
		try{
			/* import encoded public key */
			FileInputStream publicKeyfis = new FileInputStream(Settings.publicKeyFileName);
			byte[] publicEncKey = new byte[publicKeyfis.available()];  
			publicKeyfis.read(publicEncKey);
			publicKeyfis.close();

			// create a key factory
			KeyFactory keyFactory = KeyFactory.getInstance("DSA", "SUN");

			// create the pubKey according to the imported
			X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicEncKey);
			this.pubKey = keyFactory.generatePublic(pubKeySpec);

			/* import encoded private key */
			FileInputStream privateKeyfis = new FileInputStream(Settings.privateKeyFileName);
			byte[] privateEncKey = new byte[privateKeyfis.available()];  
			privateKeyfis.read(privateEncKey);
			privateKeyfis.close();

			// create the privKey according to the imported
			PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privateEncKey);        
			this.privKey = keyFactory.generatePrivate(privKeySpec);
		} catch (Exception e) {
			System.err.println("Caught exception " + e.toString());
			e.printStackTrace();
		}
	}

	/**
	 * Return the public key encoding
	 * @return encoding of public key
	 */
	public byte[] getPublicKeyEncoding(){
		return this.pubKey.getEncoded();		
	}
	
	/**
	 * 
	 * @param byteArrToSign- data to sign
	 * @return byte array representation of the signature
	 * @throws SignatureException
	 */
	public byte[] sign(byte[] byteArrToSign) throws SignatureException {
		return this.sc.sign(byteArrToSign);
	}

	public SignedData[] getSignedCommitments(Integer[] coefficients, Integer secret){
		if (!coefficients[0].equals(secret)){
			return (SignedData[])null;
		}
		BigInteger[] commitments = AnonMath.generateCommitments(coefficients);
		SignedData[] cs = new SignedData[coefficients.length];
		for (int i=0; i<coefficients.length; i++){
			try {
				cs[i] = new SignedData();
				cs[i].setSignature(sign(commitments[i].toByteArray()));
			} catch (SignatureException e) {
				System.out.println(e.getMessage());
				e.printStackTrace();
			}
		}
		return cs;
	}
	
	
	public String[] getCommitments(Integer[] coefficients, Integer secret){
		if (!coefficients[0].equals(secret)){
			return (String[])null;
		}
		BigInteger[] commitments = AnonMath.generateCommitments(coefficients);
		String[] commitmentsAsString = AnonMath.convertBigIntegerToString(commitments);
		
		return commitmentsAsString; 
	}
	
	
	
	public Integer getTemporaryId() {
		int newClientID = this.rand.nextInt(MAXID-MINID)+MINID;
		while (this.clientIDS.contains(newClientID))
			newClientID = this.rand.nextInt(MAXID-MINID)+MINID;
		this.clientIDS.add(newClientID);
		return newClientID;
	}
}
